from google.appengine.ext import db
from google.appengine.runtime.apiproxy_errors import RequestTooLargeError
from models import *
from ptzmodels import *
from ptzmodels import *
from constants import *

# user authorization methods
def authorizedToDeleteCorrespondence(corr,user):
	if not corr or not user:
		return False
	owner = corr.owner;
	mayor = corr.scene.owner;
	
	if user and (user == owner or user == mayor):
		return True
	else:
		return user.nickname() in ADMIN_EMAILS

def authorizedToDeleteMesh(mesh,user):
	if not mesh or not user:
		return False

	owner = mesh.owner;
	mayor = mesh.scene.owner;
	if user and (user != owner and mesh.scene.is_private):
	    return False
	    
	if user and (user == owner or user == mayor):
		return True
	else:
		return user.nickname() in ADMIN_EMAILS
		
def authorizedToDeleteScene(scene,user):
	if not scene or not user:
		return False
	
	#if it's a private scene, the owner can delete it  
	if scene.is_private and user == scene.owner:
		return True
	
	return user.nickname() in ADMIN_EMAILS
	
def authorizedToDeleteCalibration(calib,user):
	if not calib or not user:
		return False
	
	#owner = calib.owner;
	mayor = calib.scene.owner;
	if calib.scene.is_private and (user == mayor):#user == owner or
		return True
	
	return user.nickname() in ADMIN_EMAILS

# adds a new scene and increments the global counter.
def addScene(firstImage, url, width, height, is_private):
	def txn():
		gc = GlobalCounter.get_by_key_name('gc')
		if gc is None:
			gc = GlobalCounter(key_name='gc')
		newIndex = gc.maxScenes
		gc.numScenes += 1
		gc.maxScenes += 1
		gc.put()
		return newIndex;
	
	newIndex = db.run_in_transaction(txn)
	newScene = None

	try:
		newScene = Scene(
			key_name= 'scene' + str(newIndex),
			index=newIndex,
			firstImage = firstImage,
			url = url,
			width = width,
			height = height,
			is_private = is_private,
			calibrated = False)
		newScene.put()
	except RequestTooLargeError:
		# the image file is too large (> 1 MB, thanks a lot Google App Engine), so we can't save a copy.
		newScene = Scene(
			key_name= 'scene' + str(newIndex),
			index=newIndex,
			firstImage = '-1',
			url = url,
			width = width,
			height = height,
			is_private = is_private,
			calibrated = False)
		newScene.put();

	return newScene
	
def rateScene(sceneId, rating):
	def txn(scene,rating):
		scene = Scene.get_by_key_name('scene' + str(sceneId))
		if not scene:
			return False
		scene.ratingCount = scene.ratingCount + 1
		scene.ratingSum = scene.ratingSum + rating
		scene.ratingAverage = float(scene.ratingSum) / float(scene.ratingCount)
		scene.put()
		return True
		
	def ptztxn(scene,rating):
		scene = PTZScene.get_by_key_name('ptzscene' + str(sceneId))
		if not scene:
			return False
		scene.ratingCount = scene.ratingCount + 1
		scene.ratingSum = scene.ratingSum + rating
		scene.ratingAverage = float(scene.ratingSum) / float(scene.ratingCount)
		scene.put()
		return True
	
	
	success = db.run_in_transaction(txn, sceneId, rating)
	if not success:
		db.run_in_transaction(ptztxn, sceneId, rating)
	
# deletes a calibration and decrements the global counter.
def deleteScene(index):
	from google.appengine.api import users

	scene = Scene.get_by_key_name('scene' + str(index))
	
	user = users.get_current_user()
	
	if not authorizedToDeleteScene(scene, user):
		return False
		
	if not scene:
		return False
	
	def counttxn():
		gc = GlobalCounter.get_by_key_name('gc')
		if gc:
			gc.numScenes -= 1
			gc.put()
			return True
		else:
			return False
			
	countUpdated = db.run_in_transaction(counttxn)
	
	if countUpdated:
		scene.delete()
		return True
	else:
		return False

# adds a new correspondence and increments the global counter.
def addCorrespondence(scene, latitude, longitude, altitude, u, v):

	# first, see if any previously-made correspondences exist
	# that have the same u and v value.
	corrs = Correspondence.all().filter('scene =', scene).filter('u =', u).filter('v =', v).fetch(limit=10)
	
	if len(corrs) > 0:
		from common.helperFunctions import distance
		from common.constants import CORRESPONDENCE_EQUALITY_METERS
		import math
		
		# if any of these correspondences are sufficiently close to the one we want to
		# make, don't make a new one.
		for corr in corrs:
			groundDistance = distance(corr.latitude, corr.longitude, latitude, longitude);
			distance = math.sqrt(groundDistance**2 + (corr.altitude - altitude)**2)
			if distance < CORRESPONDENCE_EQUALITY_METERS:
				return corr

	def txn():
		gc = GlobalCounter.get_by_key_name('gc')
		if gc is None:
			gc = GlobalCounter(key_name='gc')
		newIndex = gc.maxCorrespondences
		gc.numCorrespondences += 1
		gc.maxCorrespondences += 1
		gc.put()
		return newIndex;
		
	newIndex = db.run_in_transaction(txn)
	newCorrespondence = Correspondence(
		key_name= 'corr' + str(newIndex),
		scene = scene,
		index = newIndex,
		latitude = latitude,
		longitude = longitude,
		altitude = altitude,
		u = u,
		v = v)
	newCorrespondence.put()
	return newCorrespondence
	
# deletes a correspondence and decrements the global counter.
def deleteCorrespondence(index):
	from google.appengine.api import users
	
	corr = Correspondence.get_by_key_name('corr' + str(index))
	
	user = users.get_current_user()
	if not authorizedToDeleteCorrespondence(corr, user):
		return False
		
	if not corr:
		return False
	
	if corr.parentMeshes > 0:
		return False
	
	def counttxn():
		gc = GlobalCounter.get_by_key_name('gc')
		if gc:
			gc.numCorrespondences -= 1
			gc.put()
			return True
		else:
			return False
			
	countUpdated = db.run_in_transaction(counttxn)
	if countUpdated:
		corr.delete()
		return True
	else:
		return False
	
# adds a new mesh and increments the global counter.
def addMesh(scene, name, points, triangles, correspondenceMapping, \
			daePositionArray, daeNormalArray, daeUVArray, daeTriangles, \
			nPositions, nNormals, nUVs, nTriangles, \
			originLatitude, originLongitude, originAltitude):
			
	def txn():
		gc = GlobalCounter.get_by_key_name('gc')
		if gc is None:
			gc = GlobalCounter(key_name='gc')
		newIndex = gc.maxMeshes
		gc.numMeshes += 1
		gc.maxMeshes += 1
		gc.put()
		return newIndex
	
	def corrtxn(index):
		corr = Correspondence.get_by_key_name('corr' + str(index))
		corr.parentMeshes = corr.parentMeshes + 1
		corr.put()
	
	newIndex = db.run_in_transaction(txn)
	newMesh = Mesh(
		key_name= 'mesh' + str(newIndex),
		index = newIndex,
		scene = scene,
		name  = name,
		points = points,
		triangles = triangles,
		correspondenceMapping = correspondenceMapping,
		daePositionArray = daePositionArray,
		daeNormalArray = daeNormalArray,
		daeUVArray = daeUVArray,
		daeTriangles = daeTriangles,
		originLatitude = originLatitude,
		originLongitude = originLongitude,
		originAltitude = originAltitude,
		nPositions = nPositions,
		nNormals = nNormals,
		nUVs = nUVs,
		nTriangles = nTriangles)
	newMesh.put()
	
	for corr in correspondenceMapping:
		db.run_in_transaction(corrtxn, corr)
	
	return newMesh
	
# deletes a mesh and decrements the global counter.
def deleteMesh(index):
	from google.appengine.api import users
	mesh = Mesh.get_by_key_name('mesh' + str(index))
	user = users.get_current_user()
	
	user = users.get_current_user()
	if not authorizedToDeleteMesh(mesh, user):
		return False
	
	
	if not mesh:
		return False
	
	def counttxn():
		gc = GlobalCounter.get_by_key_name('gc')
		if gc:
			gc.numMeshes -= 1
			gc.put()
			return True
		else:
			return False
			
	def decrementTxn(index):
		corr = Correspondence.get_by_key_name('corr' + str(index))
		corr.parentMeshes -= 1
		corr.put()
			
	countUpdated = db.run_in_transaction(counttxn)
	if countUpdated:
		# decrease the count in each of the correspondences.
		for corrId in mesh.correspondenceMapping:
			db.run_in_transaction(decrementTxn, corrId)
			
		mesh.delete()
		# remove any orphaned correspondences.
		for corrId in mesh.correspondenceMapping:
			corr = Correspondence.get_by_key_name('corr' + str(corrId))
			if corr.parentMeshes == 0:
				deleteCorrespondence(corr.index)
		
		return True
	else:
		return False
	
# adds a new mesh and increments the global counter.
def addOrModifyCalibration(scene, originLatitude, originLongitude, originAltitude, \
						   cameraX, cameraY, cameraZ, focalLengthX, focalLengthY, \
						   skew, imageCenterX, imageCenterY, intrinsicDivider, \
						   M, R, T):
	
	calib = Calibration.get_by_key_name('calib' + str(scene.index))
	if calib:
		calib.originLatitude = originLatitude
		calib.originLongitude = originLongitude
		calib.originAltitude = originAltitude
		calib.cameraX = cameraX
		calib.cameraY = cameraY
		calib.cameraZ = cameraZ
		calib.focalLengthX = focalLengthX
		calib.focalLengthY = focalLengthY
		calib.skew = skew
		calib.imageCenterX = imageCenterX
		calib.imageCenterY = imageCenterY
		calib.intrinsicDivider = intrinsicDivider
		calib.m11 = M[0][0]; calib.m12 = M[0][1]; calib.m13 = M[0][2]; calib.m14 = M[0][3];
		calib.m21 = M[1][0]; calib.m22 = M[1][1]; calib.m23 = M[1][2]; calib.m24 = M[1][3];
		calib.m31 = M[2][0]; calib.m32 = M[2][1]; calib.m33 = M[2][2]; calib.m34 = M[2][3];
		calib.r11 = R[0][0]; calib.r12 = R[0][1]; calib.r13 = R[0][2];
		calib.r21 = R[1][0]; calib.r22 = R[1][1]; calib.r23 = R[1][2];
		calib.r31 = R[2][0]; calib.r32 = R[2][1]; calib.r33 = R[2][2];
		calib.tx = T[0][0];
		calib.ty = T[1][0];
		calib.tz = T[2][0];
		calib.put()
	else:
		calib = Calibration(
			key_name= 'calib' + str(scene.index),
			originLatitude = originLatitude,
			originLongitude = originLongitude,
			originAltitude = originAltitude,
			cameraX = cameraX,
			cameraY = cameraY,
			cameraZ = cameraZ,
			focalLengthX = focalLengthX,
			focalLengthY = focalLengthY,
			skew = skew,
			imageCenterX = imageCenterX,
			imageCenterY = imageCenterY,
			intrinsicDivider = intrinsicDivider,
			m11 = M[0][0], m12 = M[0][1], m13 = M[0][2], m14 = M[0][3],
			m21 = M[1][0], m22 = M[1][1], m23 = M[1][2], m24 = M[1][3],
			m31 = M[2][0], m32 = M[2][1], m33 = M[2][2], m34 = M[2][3],
			r11 = R[0][0], r12 = R[0][1], r13 = R[0][2],
			r21 = R[1][0], r22 = R[1][1], r23 = R[1][2],
			r31 = R[2][0], r32 = R[2][1], r33 = R[2][2],
			tx = T[0][0],
			ty = T[1][0],
			tz = T[2][0],
			index = scene.index,
			scene = scene)
		calib.put()
	
	return calib

# deletes a calibration.
def deleteCalibration(index):
	from google.appengine.api import users
	
	calib = Calibration.get_by_key_name('calib' + str(index))
	
	user = users.get_current_user()
	if not authorizedToDeleteCalibration(calib, user):
		return False
		
	if not calib:
		return False
	

	calib.delete()
	return True

# adds a new ptz calibration or modifies an existing one.
def addOrModifyPTZCalibration(scene, originLat, originLon, originAlt, tx, ty, tz, panOffsetRad, \
							  tiltOffsetRad, rollOffsetRad, focalLengthF1, focalLengthF2, focalLengthF3):
	
	calib = PTZCalibration.get_by_key_name('ptzcalib' + str(scene.index))
	if calib:
		calib.originLatitude = originLat
		calib.originLongitude = originLon
		calib.originAltitude = originAlt
		calib.tx = tx
		calib.ty = ty
		calib.tz = tz
		calib.focalLengthF1 = focalLengthF1
		calib.focalLengthF2 = focalLengthF2
		calib.focalLengthF3 = focalLengthF3
		calib.panOffsetRad = panOffsetRad
		calib.tiltOffsetRad = tiltOffsetRad
		calib.rollOffsetRad = rollOffsetRad
		calib.put()
	else:
		calib = PTZCalibration(
			key_name= 'ptzcalib' + str(scene.index),
			originLatitude = originLat,
			originLongitude = originLon,
			originAltitude = originAlt,
			tx = tx,
			ty = ty,
			tz = tz,
			focalLengthF1 = focalLengthF1,
			focalLengthF2 = focalLengthF2,
			focalLengthF3 = focalLengthF3,
			panOffsetRad = panOffsetRad,
			tiltOffsetRad = tiltOffsetRad,
			rollOffsetRad = rollOffsetRad,
			index = scene.index,
			scene = scene)
		calib.put()
	
	return calib

# adds a new scene and increments the global counter.
def addPTZScene(firstImage, url, width, height, sourceURL, codeBaseURL, baseURL, \
				panLeftRange, panRightRange, tiltUpRange, tiltDownRange, zoomInRange, zoomOutRange, \
				cameraType, totalPanDegrees, totalTiltDegrees):
				
	def txn():
		gc = GlobalCounter.get_by_key_name('gcptz')
		if gc is None:
			gc = GlobalCounter(key_name='gcptz')
		newIndex = gc.maxScenes
		gc.numScenes += 1
		gc.maxScenes += 1
		gc.put()
		return newIndex;
	
	newIndex = db.run_in_transaction(txn)

	newScene = PTZScene(
		key_name= 'ptzscene' + str(newIndex),
		index=newIndex,
		firstImage = firstImage,
		url = url,
		width = width,
		height = height,
		calibrated = False,
		sourceURL = sourceURL,
		codeBaseURL = codeBaseURL,
		baseURL = baseURL,
		panLeftRange = panLeftRange,
		panRightRange = panRightRange,
		tiltUpRange = tiltUpRange,
		tiltDownRange = tiltDownRange,
		zoomInRange = zoomInRange,
		zoomOutRange = zoomOutRange,
		cameraType = cameraType,
		totalPanDegrees = totalPanDegrees,
		totalTiltDegrees = totalTiltDegrees)
	newScene.put()
	
	return newScene


# adds a new correspondence and increments the global counter.
def addPTZCorrespondence(scene, pan, tilt, zoom, latitude, longitude, altitude, u, v):
	def txn():
		gc = GlobalCounter.get_by_key_name('gcptz')
		if gc is None:
			gc = GlobalCounter(key_name='gcptz')
		newIndex = gc.maxCorrespondences
		gc.numCorrespondences += 1
		gc.maxCorrespondences += 1
		gc.put()
		return newIndex;
		
	newIndex = db.run_in_transaction(txn)
	newCorrespondence = PTZCorrespondence(
		key_name= 'ptzcorr' + str(newIndex),
		scene = scene,
		index = newIndex,
		latitude = latitude,
		longitude = longitude,
		altitude = altitude,
		u = u,
		v = v,
		pan = pan,
		tilt = tilt,
		zoom = zoom)
	newCorrespondence.put()
	return newCorrespondence
